package com.jinyang.jetpackdemo.activity

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.jinyang.jetpackdemo.R
import com.jinyang.jetpackdemo.paging3.Repo
import com.jinyang.jetpackdemo.paging3.RepoList
import com.jinyang.jetpackdemo.util.LjyLogUtil
import io.reactivex.Observable
import okhttp3.*
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.*
import retrofit2.http.Headers
import okhttp3.FormBody
import okhttp3.RequestBody

import retrofit2.http.PartMap

import okhttp3.ResponseBody

import retrofit2.http.Multipart

import retrofit2.http.POST

import okhttp3.MultipartBody
import retrofit2.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.http.Field
import java.io.File
import java.io.IOException
import java.lang.reflect.*


class RetrofitDemoActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_retrofit_demo)
//        initRetrofit()

        val realObject = RealObject()
        val proxyObject = ProxyObject(realObject)
        proxyObject.doSomething()

        val test = Test()
        val myProxy = DynamicProxy(test)
        val subject: Subject =
            Proxy.newProxyInstance(
                test.javaClass.classLoader,
                test.javaClass.interfaces,
                myProxy
            ) as Subject
        subject.doSomething()
        LjyLogUtil.d("subject.className:" + subject.javaClass.name)
    }

    //静态代理
    abstract class AbsObject {
        abstract fun doSomething()
    }

    class RealObject : AbsObject() {
        override fun doSomething() {
            LjyLogUtil.d("RealObject.doSomething")
        }
    }

    class ProxyObject(private val realObject: RealObject) : AbsObject() {
        override fun doSomething() {
            LjyLogUtil.d("before RealObject")
            realObject.doSomething()
            LjyLogUtil.d("after RealObject")
        }
    }

    //动态代理
    interface Subject {
        fun doSomething()
    }

    class Test : Subject {
        override fun doSomething() {
            LjyLogUtil.d("Test.doSomething")
        }
    }

    class DynamicProxy(private val target: Subject) : InvocationHandler {
        override fun invoke(proxy: Any?, method: Method?, args: Array<out Any>?): Any? {
            LjyLogUtil.d("Proxy：${proxy?.javaClass?.name}")
            LjyLogUtil.d("before target")
            //Kotlin中数组转为可变长参数，通过前面加*符号
            val invoke = method!!.invoke(target, *(args ?: emptyArray()))
            LjyLogUtil.d("after target")
            return invoke
        }
    }

    private fun initRetrofit() {
        val okHttpClient = OkHttpClient.Builder()
            .addInterceptor(object : Interceptor {
                override fun intercept(chain: Interceptor.Chain): okhttp3.Response {
                    val request = chain.request().newBuilder()
                        .addHeader(
                            "Content-Type",
                            "application/x-www-form-urlencoded; charset=UTF-8"
                        )
                        .addHeader("Accept-Encoding", "gzip, deflate")
                        .addHeader("Connection", "keep-alive")
                        .addHeader("Accept", "*/*")
                        .addHeader("Cookie", "add cookies here")
                        .build()
                    return chain.proceed(request)
                }
            })
            .build()

        val baseUrl = "https://api.github.com/"
        val retrofit = Retrofit.Builder()
            .baseUrl(baseUrl)
            .client(okHttpClient)
            // 自定义的Converter一定要放在官方提供的Converter前面
            .addConverterFactory(StringConverterFactory.create())
            .addConverterFactory(MapConverterFactory.create())
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(LjyCallAdapterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
        val apiService = retrofit.create(ApiService::class.java)
        val call = apiService.searRepos(1, 5)
        val response: Response<RepoList> = call.execute()
        if (response.isSuccessful) {
            val repo = response.body()
            LjyLogUtil.d(repo.toString())
        } else {
            LjyLogUtil.d(IOException("Unexpected code $response").message)
        }
        call.enqueue(object : Callback<RepoList> {
            override fun onResponse(call: Call<RepoList>, result: Response<RepoList>) {
                if (result.body() != null) {
                    val repoResult: RepoList = result.body()!!
                    for (it in repoResult.items) {
                        LjyLogUtil.d("${it.name}_${it.starCount}")
                    }
                }
            }

            override fun onFailure(call: Call<RepoList>, t: Throwable) {
                LjyLogUtil.d("onFailure:${t.message}")
            }

        })

        val repo = Repo(1, "name", "info", "20")
        apiService.addRepo(repo)

        val map: MutableMap<String, Any> = HashMap()
        map["key"] = "value"
        apiService.addRepo2(map)

        val body: RequestBody = RequestBody
            .create(MediaType.parse("application/json; charset=utf-8"), repo.toString())
        apiService.addRepo3(body)

        val formBody = FormBody.Builder()
            .add("key", "value")
            .build()
        apiService.addRepo4(formBody)


        val userId: RequestBody = RequestBody.create(MediaType.parse("multipart/form-data"), "1111")
        val paramsMap: MutableMap<String, RequestBody> = HashMap()
        paramsMap["userId"] = RequestBody.create(MediaType.parse("text/plain"), "123456")
        paramsMap["userName"] = RequestBody.create(MediaType.parse("text/plain"), "jinYang")
        paramsMap["taskName"] = RequestBody.create(MediaType.parse("text/plain"), "新建派单")
        val imgFile = File(externalCacheDir, "ljy.jpg")
        val requestFile: RequestBody =
            RequestBody.create(MediaType.parse("multipart/form-data"), imgFile)
        val partFile = MultipartBody.Part.createFormData("imageUrl", imgFile.name, requestFile)

        apiService.uploadImgFile(userId, paramsMap, partFile)

        val imgFile1 = File(externalCacheDir, "ljy1.jpg")
        val imgFile2 = File(externalCacheDir, "ljy2.jpg")
        val imgFile3 = File(externalCacheDir, "ljy3.jpg")
        val imageFiles = arrayOf(imgFile1, imgFile2, imgFile3)
        val parts = ArrayList<MultipartBody.Part>(imageFiles.size)
        for (i in imageFiles.indices) {
            val file: File = imageFiles[i]
            parts[i] = MultipartBody.Part.createFormData(
                "file_$i", file.name, RequestBody.create(
                    MediaType.parse("image/*"), file
                )
            )
        }
        apiService.uploadFiles(userId, parts)
    }

    interface ApiService {
        @GET("search/repositories?sort=stars&q=Android")
        fun searRepos(@Query("page") page: Int, @Query("per_page") perPage: Int): Call<RepoList>

        @GET("search/repositories?sort=stars&q=Android")
        fun searRepos(@QueryMap options: Map<String, Any>): Call<RepoList>

        @GET
        fun searRepos2(
            @Url url: String,
            @Query("page") page: Int,
            @Query("per_page") perPage: Int
        ): Observable<RepoList>

        @GET("users/{user}/repos")
        fun listRepos(@Path("user") user: String?): Call<List<Repo?>?>?

        @PATCH("{methodPath}")
        operator fun get(
            @Path("methodPath") methodPath: String?,
            @QueryMap options: Map<String?, Any?>?
        ): Observable<Map<String?, Any?>?>?

        @GET("api/items")
        fun getRepos(): Call<RepoList>

        @GET("api/items/{userId}")
        fun getItem(@Path("userId") userId: String): Call<Repo>

        /**
         * method：网络请求的方法（区分大小写）
         * path：网络请求地址路径
         * hasBody：是否有请求体
         */
        @HTTP(method = "GET", hasBody = false)
        fun getRepos(@Url url: String): Call<RepoList>

        @HTTP(method = "GET", path = "api/items/{userId}", hasBody = false)
        fun getRepos2(@Path("userId") userId: String): Call<RepoList>

        @FormUrlEncoded
        @POST("api/search")
        fun searchRepo(@Url url: String, @Field("name") repoName: String): Call<RepoList>

        @FormUrlEncoded
        @POST("api/search")
        fun searchRepo(@Url url: String, @FieldMap params: Map<String, Any>): Call<RepoList>

        @Headers("Content-Type: application/json;charset=UTF-8")
        @POST("api/search")
        fun searchRepo11(@Body params: Map<String, Any>): Observable<RepoList>

        @Headers("Content-Type: application/json;charset=UTF-8")
        @POST("api/add")
        fun addRepo(@Body repo: Repo): Call<Boolean>

        @Headers("Content-Type: application/json;charset=UTF-8")
        @POST("api/add")
        fun addRepo2(@Body params: Map<String, Any>): Call<Boolean>

        @Headers("Content-Type: application/json;charset=UTF-8")
        @POST("api/add")
        fun addRepo3(@Body body: RequestBody): Call<Boolean>

        @FormUrlEncoded
        @POST("api/add")
        fun addRepo4(@Body body: FormBody): Call<Boolean>


        @Headers("Content-Type: application/json", "Accept: application/json")
        @POST("api/search")
        fun searchRepo3(@Body params: RequestBody): Call<RepoList>

        /**
         * 下载文件
         */
        @Streaming
        @GET
        fun downloadFile(@Url url: String?): Call<ResponseBody>

        /**
         * 断点续传下载
         */
        @Streaming
        @GET
        fun downloadFile(@Header("RANGE") start: String, @Url url: String?): Call<ResponseBody>

        // {id} 表示是一个变量
        // method 的值 retrofit 不会做处理，所以要自行保证准确

        @POST("{methodPath}")
        fun postBody(
            @Path("methodPath") methodPath: String?,
            @Body route: Map<String?, Any?>?
        ): Observable<Map<String?, Any?>?>?


        /**
         * [Part] 后面支持三种类型，[RequestBody]、[okhttp3.MultipartBody.Part] 、任意类型
         * 除 [okhttp3.MultipartBody.Part] 以外，其它类型都必须带上表单字段([okhttp3.MultipartBody.Part]
         * 中已经包含了表单字段的信息)，
         */
        @POST("/form")
        @Multipart
        fun testFileUpload1(
            @Part("name") name: RequestBody?,
            @Part("age") age: RequestBody?,
            @Part file: MultipartBody.Part?
        ): Call<ResponseBody?>?

        /**
         * PartMap 注解支持一个Map作为参数，支持 [RequestBody] 类型，
         * 如果有其它的类型，会被[retrofit2.Converter]转换，如后面会介绍的 使用[com.google.gson.Gson] 的
         * [retrofit2.converter.gson.GsonRequestBodyConverter]
         * 所以[MultipartBody.Part] 就不适用了,所以文件只能用** @Part MultipartBody.Part **
         */

        @POST("upload/imgFile")
        @Multipart
        fun uploadImgFile(
            @Part("userId") userId: RequestBody?,
            @PartMap partMap: Map<String, RequestBody?>,
            @Part("file") file: MultipartBody.Part
        ): Call<ResponseBody>

        /**
         * 提交表单（可上传文件）
         * #@Part，@PartMap：用于POST文件上传 其中@Part MultipartBody.Part代表文件，@Part("key") RequestBody代表参数
         * 需要添加@Multipart表示支持文件上传的表单，Content-Type: multipart/form-data
         */
        @Multipart
        @POST
        fun upload(
            @Url url: String,
            @PartMap partMap: Map<String, RequestBody>
        ): Call<ResponseBody>


        @Multipart
        @POST("upload/files")
        fun uploadFiles(
            @Part("userId") userId: RequestBody?,
            @Part files: List<MultipartBody.Part>
        ): Call<ResponseBody>
    }

    class StringConverter : Converter<ResponseBody, String> {
        companion object {
            val INSTANCE = StringConverter()
        }

        @Throws(IOException::class)
        override fun convert(value: ResponseBody): String {
            return value.string()
        }
    }

    class StringConverterFactory : Converter.Factory() {
        companion object {
            private val INSTANCE = StringConverterFactory()
            fun create(): StringConverterFactory {
                return INSTANCE
            }
        }

        // 只实现从ResponseBody 到 String 的转换，所以其它方法可不覆盖
        override fun responseBodyConverter(
            type: Type,
            annotations: Array<Annotation?>?,
            retrofit: Retrofit?
        ): Converter<ResponseBody, *>? {
            return if (type === String::class.java) {
                StringConverter.INSTANCE
            } else null
            //其它类型不处理，返回null
        }
    }

    class LjyCall<T>(private val call: Call<T>) {
        @Throws(IOException::class)
        fun get(): T? {
            return call.execute().body()
        }
    }

    class LjyCallAdapter<R>(private val responseType: Type) : CallAdapter<R, Any> {
        override fun responseType(): Type {
            return responseType
        }

        override fun adapt(call: Call<R>): Any {
            return LjyCall(call)
        }
    }

    class LjyCallAdapterFactory : CallAdapter.Factory() {
        companion object {
            private val INSTANCE = LjyCallAdapterFactory()
            fun create(): LjyCallAdapterFactory {
                return INSTANCE
            }
        }

        override fun get(
            returnType: Type,
            annotations: Array<Annotation>,
            retrofit: Retrofit
        ): CallAdapter<R, Any>? {
            // 获取原始类型
            val rawType = getRawType(returnType)
            // 返回值必须是LjyCall并且带有泛型
            if (rawType == LjyCall::class.java && returnType is ParameterizedType) {
                val callReturnType = getParameterUpperBound(0, returnType)
                return LjyCallAdapter(callReturnType)
            }
            return null
        }
    }

    class MapConverterFactory : Converter.Factory() {
        companion object {
            fun create(): MapConverterFactory {
                return MapConverterFactory()
            }
        }

        override fun responseBodyConverter(
            type: Type,
            annotations: Array<Annotation>,
            retrofit: Retrofit
        ): Converter<ResponseBody, *> {
            return MapConverter()
        }

        class MapConverter :
            Converter<ResponseBody, Map<String, String>> {
            @Throws(IOException::class)
            override fun convert(body: ResponseBody): Map<String, String> {
                val map: MutableMap<String, String> = HashMap()
                val content = body.string()
                val keyValues = content.split("&").toTypedArray()
                for (i in keyValues.indices) {
                    val keyValue = keyValues[i]
                    val splitIndex = keyValue.indexOf("=")
                    val key = keyValue.substring(0, splitIndex)
                    val value = keyValue.substring(splitIndex + 1, keyValue.length)
                    map[key] = value
                }
                return map
            }
        }
    }

}